/** 
    
    This file is part of NxOgre.
    
    Copyright (c) 2009 Robin Southern, http://www.nxogre.org
    
    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:
    
    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    THE SOFTWARE.
    
*/

                                                                                       

#ifndef NXOGRE_SOFTBODY_H
#define NXOGRE_SOFTBODY_H

                                                                                       

#include "NxOgreStable.h"
#include "NxOgreCommon.h"
#include "NxOgreTimeListener.h"
#include "MeshHash.h"
#include "NxOgreMeshData.h"
#include "NxOgreResourceSystem.h"
#include "NxOgreResource.h"
#include "NxOgreMeshManager.h"

                                                                                       

namespace NxOgre
{

                                                                                       

/** \brief
*/
class NxOgrePublicClass SoftBody : public NonRigidBodyBasedAllocatable, public TimeListener
{
  
  NXOGRE_GC_FRIEND_NEW5
  NXOGRE_GC_FRIEND_DELETE
  
 public:
  
    /** \brief Get the renderable.
    */
    Renderable*                                 getRenderable();
  
    /** \brief Get the mesh shared by the cloth.
    */
    Mesh*                                       getMesh();
    
    /** \internal
    */
    NxSoftBody*                                 getPhysXSoftBody();

    /** \internal
    */
    MeshRenderable*                             getMeshRenderable();

    /** \brief Get the cloth's name.
    */
    String                                      getName();
  
 protected:
  
  /** \brief Use Scene::createSoftBody
  */
                                                  SoftBody(const SoftBodyDescription&, Renderable*, const Path& , Enums::Priority, Scene*);
  
  /** \brief Use Scene::destroySoftBody
  */
  virtual                                        ~SoftBody();
  
  /** \brief
  */
  bool                                            advance(float deltaTime, const Enums::Priority&, const Enums::SceneFunction&);
  
  /** \brief
  */
  void                                            updateSoftBodyMesh();

  void											  allocateReceiveBuffers(int numVertices, int numTetrahedra);
  
 protected:
  
  /** \brief Scene that the cloth belongs to.
  */
  Scene*                                        mScene;
  
  /** \brief Actual cloth.
  */
  NxSoftBody*                                   mSoftBody;
  
  /** \brief Mesh being shared by the soft body.
  */
  Mesh*                                         mMesh;

  /** \brief
  */
  Renderable*                                   mRenderable;

  /** \brief
  */
  MeshRenderable*                               mMeshData;

  /** \brief Possible name of the mesh.
  */
  String                                        mName;
  
  /** \brief Current render priority.
  */
  Enums::Priority                               mPriority;

public:

  NxOgre::vector<Vec3>							mTetraRenderVerts;
  NxOgre::vector<Vec3>							mTetraRenderNormals;

  NxMeshData									mReceiveBuffers;

  struct MeshTriangle
  {
  public:
	  void init() {
		  vertexNr[0] = -1; vertexNr[1] = -1; vertexNr[2] = -1;
		  normalNr[0] = -1; normalNr[1] = -1; normalNr[2] = -1;
		  texCoordNr[0] = -1; texCoordNr[1] = -1; texCoordNr[2] = -1;
		  materialNr = -1;
	  }
	  void set(int v0, int v1, int v2, int mat) {
		  vertexNr[0] = v0; vertexNr[1] = v1; vertexNr[2] = v2;
		  normalNr[0] = v0; normalNr[1] = v1; normalNr[2] = v2;
		  texCoordNr[0] = v0; texCoordNr[1] = v1; texCoordNr[2] = v2;
		  materialNr = mat;
	  }
	  bool containsVertexNr(int vNr) const 
	  {
		  return vNr == vertexNr[0] || vNr == vertexNr[1] || vNr == vertexNr[2];
	  }

	  // representation
	  int vertexNr[3];
	  int normalNr[3];
	  int texCoordNr[3];
	  int materialNr;

	  int edges[3];
  };

  class SurfaceMesh
  {
  public:
	  SurfaceMesh(const NxOgre::Bounds3 & bounds) : mBounds(bounds)
	  {}

	  struct MeshTetraLink
	  {
		  int tetraNr;
		  NxVec3 barycentricCoords;
	  };

	  std::vector<MeshTetraLink>					mTetraLinks;
	  std::vector<bool>								mDrainedTriVertices;
	  Bounds3										mBounds;
	  std::vector<NxVec3>							mVertices;
	  //std::vector<MeshTriangle>						mTriangles;
	  std::vector<unsigned>							mIndices;
	  std::vector<NxVec3>							mNormals;
	  std::vector<Vec2>								mTextureCoords;

  public:
	  void loadFromFlower(const Path& saveTo, const Path& flowerPath)
	  {
		  Resource * res = ResourceSystem::getSingleton()->open(flowerPath, NxOgre::Enums::ResourceAccess_ReadOnly);

		  /************************************************************************/
		  /*					Comes from NxOgreFlower.cpp						    */
		  /************************************************************************/
		  buffer<char> lineBuffer;

		  while(res->atEnd() == false)
		  {
			  NxOgre::Strings::getLine(res, lineBuffer);

			  if (lineBuffer.size() == 1) // (0 characters + 1 byte null)
				  continue;  // Skip empty lines 

			  if (lineBuffer[0] == '#')
				  continue;  // Skip full comment lines.

			  std::string line(lineBuffer.first());

			  Strings::slice_to_first_of(line, '#');
			  Strings::trim(line);

			  buffer<float> buf;
			  buffer<unsigned> buf2;
			  // vertices
			  if (Strings::starts_insensitive(line, "Vertices"))
			  {
				  Strings::slice(line, 9);
				  //Strings::split<float>(line, mesh, ",");
				  Strings::split<float>(line,buf,",");
				  for (int i=0;i<buf.size();i+=3)
				  {
					  mVertices.push_back(NxVec3(buf[i],buf[i+1],buf[i+2]));
				  }
				  continue;
			  }

			  // indices
			  if (Strings::starts_insensitive(line, "indices"))
			  {
				  Strings::slice(line,7);
				  //Strings::split<unsigned int>(line, mesh->mIndexes, ",");
				  Strings::split<unsigned int>(line,buf2,",");
				  for (int i=0;i<buf2.size();i++)
				  {
					  mIndices.push_back(buf2[i]);
				  }

				  continue;
			  }

/*
			  // normals
			  if (Strings::starts_insensitive(line, "normals"))
			  {
				  Strings::slice(line, 8);
				  //Strings::split<float>(line, mesh->mNormals, ",");
				  Strings::split<NxReal>(line,buf,",");
				  mNormals.push_back(NxVec3(buf[0],buf[1],buf[2]));
				  continue;
			  }

			  // texture coordinates
			  if (Strings::starts_insensitive(line, "texturecoords"))
			  {
				  Strings::slice(line, 14);
				  //Strings::split<float>(line, mesh->mTextureCoordinates, ",");
				  Strings::split<float>(line,buf,",");
				 // mTextureCoordinates.push_back(Vec2(buf[0],buf[1]));
				  continue;
			  }*/

		  }
	  }

	  void buildTetraLinks(const NxVec3 *vertices, const NxU32 *indices, const NxU32 numTets)
	  {
		  mTetraLinks.clear();

		  MeshHash* hash = new MeshHash();

		  // hash tetrahedra for faster search
		  hash->setGridSpacing(mBounds.min.distance(mBounds.max) * 0.1f);

		  for (NxU32 i = 0; i < numTets; i++) {
			  const NxU32 *ix = &indices[4*i];
			  NxBounds3 tetraBounds;
			  tetraBounds.setEmpty();
			  tetraBounds.include(vertices[*ix++]);
			  tetraBounds.include(vertices[*ix++]);
			  tetraBounds.include(vertices[*ix++]);
			  tetraBounds.include(vertices[*ix++]);
			  hash->add(tetraBounds, i);
		  }

		  for (NxU32 i = 0; i < mVertices.size(); i++) {
			  // prepare datastructure for drained tetras
			  mDrainedTriVertices.push_back(false);

			  MeshTetraLink tmpLink;

			  NxVec3 triVert = mVertices[i];
			  std::vector<int> itemIndices;
			  hash->queryUnique(triVert, itemIndices);

			  NxReal minDist = 0.0f;
			  NxVec3 b;
			  int num, isize;
			  num = isize = (int) itemIndices.size();
			  if (num == 0) num = numTets;

			  for (int i = 0; i < num; i++) {
				  int j = i;
				  if (isize > 0) j = itemIndices[i];

				  const NxU32 *ix = &indices[j*4];
				  const NxVec3 &p0 = vertices[*ix++];
				  const NxVec3 &p1 = vertices[*ix++];
				  const NxVec3 &p2 = vertices[*ix++];
				  const NxVec3 &p3 = vertices[*ix++];

				  NxVec3 b = computeBaryCoords(triVert, p0, p1, p2, p3);

				  // is the vertex inside the tetrahedron? If yes we take it
				  if (b.x >= 0.0f && b.y >= 0.0f && b.z >= 0.0f && (b.x + b.y + b.z) <= 1.0f) {
					  tmpLink.barycentricCoords = b;
					  tmpLink.tetraNr = j;
					  break;
				  }

				  // otherwise, if we are not in any tetrahedron we take the closest one
				  NxReal dist = 0.0f;
				  if (b.x + b.y + b.z > 1.0f) dist = b.x + b.y + b.z - 1.0f;
				  if (b.x < 0.0f) dist = (-b.x < dist) ? dist : -b.x;
				  if (b.y < 0.0f) dist = (-b.y < dist) ? dist : -b.y;
				  if (b.z < 0.0f) dist = (-b.z < dist) ? dist : -b.z;

				  if (i == 0 || dist < minDist) {
					  minDist = dist;
					  tmpLink.barycentricCoords = b;
					  tmpLink.tetraNr = j;
				  }
			  }

			  mTetraLinks.push_back(tmpLink);
		  }
		  delete hash;
	  }

	  void simulate(const NxMeshData & tetraMeshData)
	  {
		 // if (mTetraLinks.size() != mVertices.size()) return false;

		  NxU32 numVertices = *tetraMeshData.numVerticesPtr;
		  NxU32 numTetrahedra = *tetraMeshData.numIndicesPtr / 4;
		  const NxVec3 *vertices = (NxVec3*)tetraMeshData.verticesPosBegin;
		  NxU32* indices = (NxU32*)tetraMeshData.indicesBegin;

		  for (int i = 0; i < (int)mVertices.size(); i++) {
			  MeshTetraLink &link = mTetraLinks[i];

			  if (!mDrainedTriVertices[i]) {
				  // only done if tetra was not drained before
				  const NxU32 *ix = &indices[4*link.tetraNr];

				  if (*ix == *(ix + 1)) {
					  // this tetra was drained
					  //removeTrisRelatedToVertex(i);
					  mDrainedTriVertices[i] = true;
					  continue;
				  }

				  const NxVec3 &p0 = vertices[*ix++];
				  const NxVec3 &p1 = vertices[*ix++];
				  const NxVec3 &p2 = vertices[*ix++];
				  const NxVec3 &p3 = vertices[*ix++];

				  NxVec3 &b = link.barycentricCoords;
				  mVertices[i] = p0 * b.x + p1 * b.y + p2 * b.z + p3 * (1.0f - b.x - b.y - b.z);
			  }
		  }

		  /*mNormals.resize(mVertices.size());
		  int i;
		  for (i = 0; i < (int)mNormals.size(); i++) 
			  mNormals[i].zero();
		  NxVec3 n;
		  for (i = 0; i < (int)mTriangles.size(); i++) 
		  {
			  MeshTriangle &mt = mTriangles[i];
			  mt.normalNr[0] = mt.vertexNr[0];
			  mt.normalNr[1] = mt.vertexNr[1];
			  mt.normalNr[2] = mt.vertexNr[2];
			  n.cross(mVertices[mt.vertexNr[1]] - mVertices[mt.vertexNr[0]], 
				  mVertices[mt.vertexNr[2]] - mVertices[mt.vertexNr[0]]);
			  mNormals[mt.normalNr[0]] += n;
			  mNormals[mt.normalNr[1]] += n;
			  mNormals[mt.normalNr[2]] += n;
		  }
		  for (i = 0; i < (int)mNormals.size(); i++) 
			  mNormals[i].normalize();*/
	  }

	  NxVec3 computeBaryCoords(NxVec3 vertex, NxVec3 p0, NxVec3 p1, NxVec3 p2, NxVec3 p3) 
	  {
		  NxVec3 baryCoords;

		  NxVec3 q  = vertex-p3;
		  NxVec3 q0 = p0-p3;
		  NxVec3 q1 = p1-p3;
		  NxVec3 q2 = p2-p3;

		  NxMat33 m;
		  m.setColumn(0,q0);
		  m.setColumn(1,q1);
		  m.setColumn(2,q2);

		  NxReal det = m.determinant();

		  m.setColumn(0, q);
		  baryCoords.x = m.determinant();

		  m.setColumn(0, q0); m.setColumn(1,q);
		  baryCoords.y = m.determinant();

		  m.setColumn(1, q1); m.setColumn(2,q);
		  baryCoords.z = m.determinant();

		  if (det != 0.0f)
			  baryCoords /= det;

		  return baryCoords;
	  }

	 /* void removeTrisRelatedToVertex(const int vertexIndex) 
	  {
		  for (NxU32 currentTri = 0; currentTri < mTriangles.size(); currentTri++) {
			  if (mTriangles[currentTri].containsVertexNr(vertexIndex)) {
				  // set all 3 vertices to vertex[0]
				  mTriangles[currentTri].vertexNr[1] = mTriangles[currentTri].vertexNr[0];
				  mTriangles[currentTri].vertexNr[2] = mTriangles[currentTri].vertexNr[0];
			  }
		  }
	  }*/

  };

  SurfaceMesh *									mSurfaceMesh;
 

}; // class Cloth

                                                                                       

} // namespace NxOgre

                                                                                       

#endif


/*
NxOgre::Strings::getLine(res, lineBuffer);

if (lineBuffer.size() == 1) // (0 characters + 1 byte null)
continue;  // Skip empty lines 

if (lineBuffer[0] == '#')
continue;  // Skip full comment lines.

std::string line(lineBuffer.first());

Strings::slice_to_first_of(line, '#');
Strings::trim(line);

buffer<float> buf;
buffer<unsigned> buf2;
// vertices
if (Strings::starts_insensitive(line, "v "))
{
	Strings::slice(line, 2);
	//Strings::split<float>(line, mesh, ",");
	Strings::split<float>(line,buf," ");
	mVertices.push_back(NxVec3(buf[0],buf[1],buf[2]));
	continue;
}

// indices
if (Strings::starts_insensitive(line, "f "))
{
	Strings::slice(line,2);
	//Strings::split<unsigned int>(line, mesh->mIndexes, ",");
	//Strings::split<unsigned int>(line,buf2,"/");
	int a[9];
	sscanf(line.c_str(), "%d/%d/%d %d/%d/%d %d/%d/%d", &a[0], &a[1], &a[2], &a[3], &a[4], &a[5], &a[6], &a[7], &a[8]);
	for (int i=0;i<9;i++)
	{
		mIndices.push_back(a[i]);
	}
	continue;
}

// normals
if (Strings::starts_insensitive(line, "nv "))
{
	Strings::slice(line, 3);
	//Strings::split<float>(line, mesh->mNormals, ",");
	Strings::split<NxReal>(line,buf," ");
	mNormals.push_back(NxVec3(buf[0],buf[1],buf[2]));
	continue;
}

// texture coordinates
if (Strings::starts_insensitive(line, "texturecoords"))
{
	Strings::slice(line, 14);
	//Strings::split<float>(line, mesh->mTextureCoordinates, ",");
	Strings::split<float>(line,buf,",");
	// mTextureCoordinates.push_back(Vec2(buf[0],buf[1]));
	continue;
}*/
